package com.jspx.txweb.evasive;

import com.caucho.util.ConcurrentArrayList;
import com.jspx.boot.EnvFactory;
import com.jspx.boot.environment.Environment;
import com.jspx.boot.environment.EnvironmentTemplate;
import com.jspx.cache.CacheManager;
import com.jspx.cache.JSCacheManager;
import com.jspx.cache.store.MemoryStore;
import com.jspx.sober.util.DataMap;
import com.jspx.txweb.config.ResultConfigBean;
import com.jspx.txweb.dao.GenericDAO;
import com.jspx.txweb.dispatcher.Dispatcher;
import com.jspx.txweb.evasive.condition.*;
import com.jspx.txweb.util.RequestUtil;
import com.jspx.txweb.util.TXWebUtil;
import com.jspx.utils.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * Created by Administrator on 2017/6/15.
 * 回避管理器
 *
 */
public class EvasiveManager {

    static protected boolean debug = false;
    private static final Logger log = LoggerFactory.getLogger(EvasiveManager.class);
    //private List<EvasiveRule> evasiveRuleList = Collections.synchronizedList(new ArrayList<EvasiveRule>());
    //private List<QueryBlack> queryBlackRuleList = Collections.synchronizedList(new  ArrayList<QueryBlack>());

    private Collection<EvasiveRule> evasiveRuleList = new ConcurrentArrayList<EvasiveRule>(EvasiveRule.class);
    private Collection<QueryBlack> queryBlackRuleList =  new ConcurrentArrayList<QueryBlack>(QueryBlack.class);


    private Map<String,ResultConfigBean> resultConfigMap = new ConcurrentHashMap<String,ResultConfigBean>();
    //拦截的ip,放入的时间
    private static Map<String,EvasiveIp> blackIpList =  new ConcurrentHashMap<String, EvasiveIp>();
    //白名单
    private static String[] whiteList =  new String[0];
    //黑名单
    private static String[] blackList =  new String[0];

    final private static String blackResult = "black";

    private static boolean evasiveExcludeFilter = true;
    private static String[] insecureUrlKeys = null;
    private static String[] insecureQueryStringKeys = null;


    //判断程序
    private static Map<String,Decide> decideList = new HashMap<String, Decide>();
    static {
        decideList.put("session",new SessionDecide());
        decideList.put("cookie",new CookieDecide());
        decideList.put("useragent",new UserAgentDecide());
        decideList.put("ip",new IpDecide());
        decideList.put("parameter",new ParameterDecide());
        decideList.put("script",new ScriptDecide());
        decideList.put("sql",new SqlDecide());
        decideList.put("referer",new RefererDecide());
    }

    private static EvasiveManager instance =  new EvasiveManager();
    public static EvasiveManager getInstance() {
        return instance;
    }
    private EvasiveManager()
    {
        reload();
    }

    private void reload()
    {
        EnvironmentTemplate envTemplate = EnvFactory.getEnvironmentTemplate();
        boolean useEvasive = envTemplate.getBoolean(Environment.useEvasive);
        if (!useEvasive) return;
        evasiveRuleList.clear();
        queryBlackRuleList.clear();
        resultConfigMap.clear();

        Configuration configuration = EvasiveConfiguration.getInstance();
        evasiveRuleList.addAll(configuration.getEvasiveRuleList());
        queryBlackRuleList.addAll(configuration.getQueryBlackRuleList());

        for (ResultConfigBean resultConfigBean:configuration.getResultConfigList())
        {
            resultConfigMap.put(resultConfigBean.getName(),resultConfigBean);
        }

        whiteList = configuration.getWhiteList();
        blackList = configuration.getBlackList();

        insecureUrlKeys = ArrayUtil.deleteRepeated(configuration.getInsecureUrlKeys());
        insecureQueryStringKeys = ArrayUtil.deleteRepeated(configuration.getInsecureQueryStringKeys());

        log.info("white list，白名单:"+ ArrayUtil.toString(whiteList,StringUtil.SEMICOLON));
        //合并字符串里边相同的内容
        log.info("black list，黑名单:"+ ArrayUtil.toString(blackList,StringUtil.SEMICOLON));

        Map<String, Object> valueMap = createEnvironment();
        CacheManager cacheManager = JSCacheManager.getCacheManager();
        for (EvasiveRule evasiveRule:evasiveRuleList)
        {

            try {
                evasiveRule.setUrl(EnvFactory.getPlaceholder().processTemplate(valueMap,evasiveRule.getUrl()));
            } catch (Exception e) {
                log.error("evasive rule " + evasiveRule.getName()+" config url is error,回避过滤的规则URL部分配置错误规则名称:"+evasiveRule.getName(),e);
                e.printStackTrace();
            }

            //如果没有配置缓存，这里将创建缓存
            if (!cacheManager.containsKey(evasiveRule.getCacheName()))
            {
                MemoryStore store = new MemoryStore();
                try
                {
                    cacheManager.createCache(store,evasiveRule.getCacheName(),evasiveRule.getInterval(),evasiveRule.getCacheSize(),false,System.getProperty("user.dir"));
                } catch (Exception e)
                {
                    log.error("evasive create cache "+evasiveRule.getCacheName()+" fail,创建缓存失败缓存名称:"+evasiveRule.getCacheName(),e);
                    e.printStackTrace();
                }
            }
        }

        debug = envTemplate.getBoolean(Environment.logDebugEvasive);
        evasiveExcludeFilter =  envTemplate.getBoolean(Environment.evasiveExcludeFilter);


    }

    private static Map<String, Object> createEnvironment() {
        EnvironmentTemplate envTemplate = EnvFactory.getEnvironmentTemplate();
        Map<String, Object> envParams = new HashMap<String, Object>();
        envParams.put(Environment.filterSuffix, Dispatcher.getFilterSuffix());
        envParams.put(Environment.templateSuffix, envTemplate.getString(Environment.templateSuffix));
        envParams.put(Environment.encode, Dispatcher.getEncode());
        envParams.put(Environment.remoteHostUrl, envTemplate.getString(Environment.remoteHostUrl, ""));
        envParams.put(Environment.scriptPath, envTemplate.getString(Environment.scriptPath, "/script"));
        envParams.put(Environment.sitePath, envTemplate.getString(Environment.sitePath, "/"));
        envParams.put(Environment.userLoginUrl, envTemplate.getString(Environment.userLoginUrl, "/user/index."+envTemplate.getString(Environment.filterSuffix)));
        envParams.put("date", new Date());
        return envParams;
    }

    private boolean conditionDecides(List<Condition>  conditions,String logic,HttpServletRequest request, HttpServletResponse response)
    {
        if (conditions.isEmpty()) return true;
        //logicArray 保存的是每个逻辑关系的计算结果
        Map<String, Object> envParams =createEnvironment();
        envParams.put("request",request);
        envParams.put("response",response);
        envParams.put("ip",RequestUtil.getRemoteAddr(request));
        envParams.put("remoteHost",request.getRemoteHost());
        envParams.put("method",request.getMethod());


        int[] logicArray = ArrayUtil.getInitedIntArray(conditions.size(),0);
        for (int i=0;i<logicArray.length;i++)
        {
            Condition condition = conditions.get(i);
            Decide decide = decideList.get(condition.getRuleType().toLowerCase());
            //如果找不到默认就是0
            if (decide==null) continue;

            decide.setRequest(request);
            decide.setResponse(response);
            String runScript = null;
            try {
                runScript = EnvFactory.getPlaceholder().processTemplate(envParams, condition.getScript());
            } catch (Exception e) {
                log.error("condition decides script content has error " + condition.getScript());
                e.printStackTrace();
            }
            decide.setContent(runScript);
            logicArray[i] = decide.execute()?1:0;
            //判断and 有一个不成立,就不判断其他的了
            if ("and".equalsIgnoreCase(logic)&&logicArray[i]==0) return false;
        }
        return "or".equalsIgnoreCase(logic) && ArrayUtil.contains(logicArray, 1);
    }


    /**
     *
     * @param ip ip
     * @return 判断是否在白名单
     */
    private boolean isInWhiteList(String ip)
    {
        if (ArrayUtil.isEmpty(whiteList)) return false;
        for (String ipEx:whiteList)
        {
            if (IpUtil.interiorly(ipEx,ip)) return true;
        }
        return false;
    }

    /**
     *  判断是否在黑名单
     * @param ip
     * @return
     */
    private boolean isInBlackList(String ip)
    {
        if (ArrayUtil.isEmpty(blackList)) return false;
        for (String ipEx:blackList)
        {
            if (IpUtil.interiorly(ipEx,ip)) return true;
        }
        return false;
    }

    /**
     *  查询方式判断
     * ip 查询黑名单,根据条件查询数据库，ip访问记录表，来分析是否有拦截
     */
    private void runQueryBlack()
    {
        for (QueryBlack queryBlack:queryBlackRuleList)
        {
            //执行周期为5分钟执行一次
            if (System.currentTimeMillis()-queryBlack.getLastQueryTimeMillis()<DateUtil.MINUTE*2) continue;
            queryBlack.setLastQueryTimeMillis(System.currentTimeMillis());
            GenericDAO genericDAO = (GenericDAO) EnvFactory.getBeanFactory().getBean(Environment.genericDAO);
            if (genericDAO==null)
            {
                log.error("not find genericDAO,没有找到 genericDAO配置");
                return;
            }
            String sql = queryBlack.getSql();
            if (StringUtil.isNULL(sql))
            {
                log.error(queryBlack.getName() + "not find sql,没有查询sql");
                return;
            }

            try
            {
                sql = EnvFactory.getPlaceholder().processTemplate(createEnvironment(),sql);
            } catch (Exception e) {
                e.printStackTrace();
                log.error(queryBlack.getName() + " sql " + sql,e);
            }
            if (debug) log.debug(sql);
            List<DataMap> dataMapList = genericDAO.prepareQuery(sql,null);
            if (dataMapList==null||dataMapList.isEmpty()) continue;
            List<String> saveList = new ArrayList<>();
            for (DataMap dataMap:dataMapList)
            {
                String qIp = dataMap.getValue(queryBlack.getIpField());
                int qTimes = dataMap.getInt(queryBlack.getTimesField());
                //满足次数,并且不在黑名单
                if (qTimes>queryBlack.getMinTimes()&&!blackIpList.containsKey(qIp))
                {
                    saveList.add(qIp);
                }
                if (saveList.size()>queryBlack.getBlackSize()) break;
            }
            //保持到黑名单
            if (!saveList.isEmpty())
            {
                for (String sIp:saveList)
                {
                    if (queryBlack.getImprisonSecond()<=0)
                    {
                        if (!ArrayUtil.contains(blackList,sIp))
                            blackList = ArrayUtil.add(blackList,sIp);
                    } else {
                        EvasiveIp evasiveIp = new EvasiveIp();
                        evasiveIp.setId(IpUtil.toLong(sIp));
                        evasiveIp.setIp(sIp);
                        evasiveIp.setTimes(1);
                        evasiveIp.setImprisonSecond(queryBlack.getImprisonSecond());
                        evasiveIp.setResult(queryBlack.getResult());
                         blackIpList.put(sIp,evasiveIp);
                    }
                }
            }
        }
    }

    /**
     * 判断规则
     */
    private void runEvasiveRule(HttpServletRequest request, HttpServletResponse response)
    {
        String localUrl = request.getRequestURI(); //只是文件部分,没有参数
        if (debug) log.debug("evasive check url:" + localUrl);
        if (localUrl!=null) localUrl = localUrl.toLowerCase();
        else localUrl = "";
        String ip = RequestUtil.getRemoteAddr(request);
        for (EvasiveRule evasiveRule:evasiveRuleList)
        {
            if (!"*".equals(evasiveRule.getMethod())&&!request.getMethod().equalsIgnoreCase(evasiveRule.getMethod())) continue;
            if (!"*".equals(evasiveRule.getUrl())&&!localUrl.matches(evasiveRule.getUrl())) continue;
            EvasiveIp evasiveIp = (EvasiveIp)JSCacheManager.get(evasiveRule.getCacheName(),ip);
            if (evasiveIp==null)
            {
                evasiveIp = new EvasiveIp();
                evasiveIp.setId(IpUtil.toLong(ip));
                evasiveIp.setIp(ip);
                evasiveIp.setTimes(1);
                evasiveIp.setImprisonSecond(evasiveRule.getImprisonSecond());
                evasiveIp.setResult(evasiveRule.getResult());
                JSCacheManager.put(evasiveRule.getCacheName(),ip,evasiveIp);
            }
            //判断条件满足到才能加


            //判断是否满足脚本条件
            if (conditionDecides(evasiveRule.getConditions(),evasiveRule.getLogic(),request,response))
            {
                int times = evasiveIp.updateTimes();
                if (debug)
                    log.debug(ip+":" + times);
            }
            //判断是否满足脚本条件

            //如果创建的时间大于一个周期时间, 访问次数大于最大允许次数的数据，清空，重置,从黑名单里边放出来
            if (evasiveIp.getTimes()>evasiveRule.getMaxTimes())
            {
                if (System.currentTimeMillis()-evasiveIp.getCreateTimeMillis()>=evasiveRule.getInterval()* DateUtil.SECOND)
                {
                    evasiveIp.setCreateTimeMillis(System.currentTimeMillis());
                    evasiveIp.setTimes(0);
                } else {
                    if (evasiveIp.getImprisonSecond()<=0)
                    {
                        if (!ArrayUtil.contains(blackList,ip))
                            blackList = ArrayUtil.add(blackList,ip);
                    } else {
                        evasiveIp = new EvasiveIp();
                        evasiveIp.setId(IpUtil.toLong(ip));
                        evasiveIp.setIp(ip);
                        evasiveIp.setTimes(1);
                        evasiveIp.setImprisonSecond(evasiveIp.getImprisonSecond());
                        evasiveIp.setResult(evasiveRule.getResult());
                        if (!blackIpList.containsKey(ip))
                            blackIpList.put(ip,evasiveIp);
                    }
                    //删除计数器,只是为了减少内存占用
                    JSCacheManager.remove(evasiveRule.getCacheName(),ip);
                }
                return;
            }
        } //for (EvasiveRule evasiveRule:evasiveRuleList)
    }
    /**
     * 转发器
     * @param request 请求
     * @param response 应答
     * @return boolean   返回true:表示已经拦截,false 表示不拦截
     */
    public boolean execute(HttpServletRequest request, HttpServletResponse response) {
        String localUrl = request.getRequestURI(); //只是文件部分,没有参数
        if (debug) log.debug("evasive check url:" + localUrl);
        if (localUrl!=null) localUrl = localUrl.toLowerCase();
        else localUrl = "";

        ///////////////这里是网站的安全隔离 begin
        //过滤URL非法字符串
        if (!ArrayUtil.isEmpty(insecureUrlKeys))
        {
            for (String key:insecureUrlKeys)
            {
                if (localUrl.contains(key))
                {
                    TXWebUtil.errorPrint("禁止使用不安全的URL文件名访问", response,500);
                    return true;
                }
            }
        }
        //过滤参数中非法字符串
        if (!ArrayUtil.isEmpty(insecureQueryStringKeys))
        {
            String queryString = request.getQueryString(); //只是参数部分
            if (queryString!=null)
            {
                queryString = URLUtil.getURLDecoder(queryString,Dispatcher.getEncode());
                queryString = queryString.toLowerCase();
                for (String key:insecureQueryStringKeys)
                {
                    if (queryString.contains(key))
                    {
                        TXWebUtil.errorPrint("禁止使用不安全的参数访问", response,500);
                        return true;
                    }
                }
            }
        }
        ///////////////这里是网站的安全隔离 end
        //优化排除不需要判断的URL,如果tomcat单独作为web服务器的时候很有用
        if (evasiveExcludeFilter&&localUrl.matches( "/\\S+\\.\\S+."+ Dispatcher.getFilterSuffix())) return false;

        //白名单检测begin
        String ip = RequestUtil.getRemoteAddr(request);
        if (isInWhiteList(ip)) return false;
        //白名单检测end

        //黑名单判断
        if (isInBlackList(ip))
        {
            printBlackError( request,  response);
            return true;
        }

        EvasiveIp blackEvasiveIp = blackIpList.get(ip);
        if (blackEvasiveIp!=null)
        {
            if (System.currentTimeMillis()-blackEvasiveIp.getCreateTimeMillis()>blackEvasiveIp.getImprisonSecond()*DateUtil.SECOND)
            {
                //到期的黑名单人员放出来
                blackIpList.remove(ip);
                return false;
            }
            //这里是要拦截的数据begin
            printError(request,response,blackEvasiveIp);
            return true;
            //这里是要拦截的数据end
        }

        //计算查询表方式拦截 begin
        runQueryBlack();
        //计算查询表方式拦截 begin

        //规则判断 begin
        runEvasiveRule(request, response);
        //规则判断 end
        return false;
    }
    private void printBlackError(HttpServletRequest request, HttpServletResponse response)  {
        String ip = RequestUtil.getRemoteAddr(request);
        //为了支持脚本和更多功能
        Map<String, Object> envParams = createEnvironment();
        envParams.put("request",request);
        envParams.put("response",response);
        envParams.put("ip",ip);
        envParams.put("waitSecond",Long.MAX_VALUE);
        envParams.put("waitTime","forever,永远");
        ResultConfigBean resultConfig = resultConfigMap.get(blackResult);
        doResult( request,  response,envParams,resultConfig);
    }

    private void printError(HttpServletRequest request, HttpServletResponse response,EvasiveIp blackEvasiveIp)  {
        String ip = RequestUtil.getRemoteAddr(request);
        //为了支持脚本和更多功能
        Map<String, Object> envParams = createEnvironment();
        envParams.put("request",request);
        envParams.put("response",response);
        envParams.put("ip",ip);
        long currentTimeMillis = System.currentTimeMillis();
        long waitTime = blackEvasiveIp.getImprisonSecond()*DateUtil.SECOND - (currentTimeMillis-blackEvasiveIp.getCreateTimeMillis());
        if (waitTime<0) waitTime = 0;
        envParams.put("waitSecond",(waitTime/DateUtil.SECOND));
        envParams.put("waitTime",DateUtil.getTimeMillisFormat(waitTime,"zh"));
        ResultConfigBean resultConfig = resultConfigMap.get(blackEvasiveIp.getResult());
        doResult( request,  response,envParams,resultConfig);
    }


    static private void doResult(HttpServletRequest request, HttpServletResponse response,Map<String, Object> envParams,ResultConfigBean resultConfig) {
        if (resultConfig==null)
        {
            TXWebUtil.print("evasive result not find config<br>配置有错误,稍后后再试<br>",TXWebUtil.htmlType,response);
            return;
        }

        String resultSrc = StringUtil.empty;
        try {
            resultSrc = EnvFactory.getPlaceholder().processTemplate(envParams,resultConfig.getValue());
        } catch (Exception e) {
            if (debug)
            {
                log.error("getPlaceholder processTemplate" + resultConfig.getValue());
                TXWebUtil.errorPrint("evasive安全配置有错误:" + StringUtil.toBrLine(e.getMessage()),response,resultConfig.getStatus());
            } else
                TXWebUtil.errorPrint("evasive安全配置有错误",response,resultConfig.getStatus());
        }
        if (TXWebUtil.redirectType.equalsIgnoreCase(resultConfig.getType()))
        {
            try {
                if (!StringUtil.isNULL(resultSrc)) response.sendRedirect(resultSrc);
            } catch (Exception e) {
                if (debug)
                {
                    log.error("sendRedirect " + resultConfig.getValue());
                    TXWebUtil.errorPrint("sendRedirect " + resultConfig.getValue() + " " + StringUtil.toBrLine(e.getMessage()),response,resultConfig.getStatus());
                } else
                    TXWebUtil.errorPrint("转发地址错误:" + resultSrc,response,resultConfig.getStatus());
            }
        } else
        if (TXWebUtil.chainType.equalsIgnoreCase(resultConfig.getType()))
        {
            try {
                if (!StringUtil.isNULL(resultSrc))  request.getRequestDispatcher(resultSrc).forward(request,response);
            } catch (Exception e) {
                if (debug)
                {
                    TXWebUtil.errorPrint("chain forward " + resultConfig.getValue() + " fail",response,resultConfig.getStatus());
                    log.error("chain forward" + resultConfig.getValue());
                } else
                {
                    TXWebUtil.errorPrint("chain forward fail",response,resultConfig.getStatus());
                }
            }
        } else
        if (TXWebUtil.htmlType.equalsIgnoreCase(resultConfig.getType()))
        {
            //输出HTML代码
            try {
                String str = EnvFactory.getPlaceholder().processTemplate(envParams,resultSrc);
                TXWebUtil.print(str,TXWebUtil.htmlType,response,resultConfig.getStatus());
            } catch (Exception e) {
                e.printStackTrace();
                log.error("evasive config error :" + resultConfig.getName());
            }
        }else
        if (TXWebUtil.xmlType.equalsIgnoreCase(resultConfig.getType()))
        {
            try {
                String str = EnvFactory.getPlaceholder().processTemplate(envParams,resultSrc);
                TXWebUtil.print(str,TXWebUtil.xmlType,response,resultConfig.getStatus());
            } catch (Exception e) {
                e.printStackTrace();
                log.error("evasive config error :" + resultConfig.getName());
            }
        }else
        if (TXWebUtil.jsonType.equalsIgnoreCase(resultConfig.getType()))
        {
            try {
                String str = EnvFactory.getPlaceholder().processTemplate(envParams,resultSrc);
                TXWebUtil.print(str,TXWebUtil.jsonType,response,resultConfig.getStatus());
            } catch (Exception e) {
                e.printStackTrace();
                log.error("evasive config error :" + resultConfig.getName());
            }

        } else
        if (TXWebUtil.javascriptType.equalsIgnoreCase(resultConfig.getType())||"script".equalsIgnoreCase(resultConfig.getType()))
        {
            try {
                String str = EnvFactory.getPlaceholder().processTemplate(envParams,resultSrc);
                response.setStatus(resultConfig.getStatus());
                TXWebUtil.print(str,TXWebUtil.javascriptType,response);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("evasive config error :" + resultConfig.getName());
            }
        }  else {
            //消息类型
            TXWebUtil.errorPrint(resultConfig.getValue(),response,resultConfig.getStatus());
        }
    }
    public String[] getWhiteList()
    {
        return whiteList;
    }


    public String[] getBlackList()
    {
        return blackList;
    }

    public  Collection<EvasiveIp> getBlackIpList()
    {
        return blackIpList.values();
    }

    public  Collection<EvasiveRule> getEvasiveRuleList()
    {

        return evasiveRuleList;
    }
    public  Collection<QueryBlack> getQueryBlackRuleList()
    {
        return queryBlackRuleList;
    }

    public Collection<ResultConfigBean> getResultConfigList()
    {
        return resultConfigMap.values();
    }


    public void shutdown()
    {
        evasiveRuleList.clear();
        blackIpList.clear();
    }
}